package semantic;

import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Stack;

import astnode.DeclarationNode;

import common.LogPrinter;

/**
 * Responsible for check 1, 2, 20
 * @author Boshi Li
 *
 */
public class SymbolTable {
	private int[] AT = null;			//AT = access table
	private Stack<Identification> IT = new Stack<Identification>();			//IT = identification table
	private LogPrinter logPrinter = null;
	private PrintStream errPrinter = null;
	private boolean containsError = false;
	private ArrayList<String> spellingTable = null;
	
	public SymbolTable(){
		createEntry( 0, null , 0 , 0 );
	}
	
	/**
	 * set the length of spelling table to Access Table
	 * @param length
	 */
	public SymbolTable( int length, LogPrinter log, PrintStream errWriter, ArrayList<String> spellingTable ){
		AT = new int[length];
		//clear the AT
		for( int i=0; i<length;i++ ){
			AT[i] = 0;
		}
		//push line 0 to IT
		//line 0's level is 0 since then we can judge double declaration by level simply
		IT.push( new Identification( -1 , null, 0, 0));
		
		this.logPrinter = log;
		this.errPrinter = errWriter;
		this.spellingTable = spellingTable;
	}
	
	/**
	 * create entry in the symbol table. If it found errors it will report it.
	 * @param level
	 * @param decPtr
	 * @param lexI
	 * @param lineNum
	 */
	public void createEntry( int level, DeclarationNode decPtr, int lexI , int lineNum ){
		//get idI from AT
		int idI = AT[lexI];
		//judge if it has been declared before
		if( idI!=-1 && IT.get( idI ).getLevel() == level ){
			//idea:
			//idI ==0 means it has not been declared
			//get(idI).getLevel == level means it has been declared
			//if line 0's level is 0, there will be a mistake that each declaration is double declaration 
			//since each declaration level is 0 which is same as line 0 all the time
			
			//condition: double declaration error
			logPrinter.println( String.format( "Line %d: Double Declaration Error: %d => %s", lineNum, lexI , spellingTable.get(lexI) ) );
			errPrinter.println(  String.format( "Line %d: Double Declaration Error: %d => %s", lineNum, lexI , spellingTable.get(lexI) ) );
			containsError =true;
		}else{
			//condition: no declaration before or idI==-1 which represent it has not been declared and already been reported
			//note: In this condition, idI represent 'next' now 
			Identification id = new Identification( level, decPtr, idI, lexI );
			IT.push(id);
			//replace idI in AT, note:  IT.size()-1 is the last index of the stack
			AT[lexI] = IT.size() - 1;
		}
	}
	
	/**
	 * pop the all the id of that level in IT 
	 * @param level must be the IT's max or IT's max+1
	 */
	public void pop(int level ){
		while( IT.lastElement().getLevel() == level ){
			int lexI = IT.lastElement().getLexI();
			int idI = IT.lastElement().getNext();
			AT[lexI]=idI;
			IT.pop();
		}
	}
	
	/**
	 * @param lexI lexical index
	 * @return pointer to declaration
	 * 				 null if it's undeclared	 
	 */
	public DeclarationNode getDec( int lexI, int lineNum ){
		if( AT[lexI]==0 ){
			//condition: undeclared variable
			logPrinter.println( String.format("Line %d: Undeclared Indetification: %d => %s", lineNum, lexI, spellingTable.get(lexI)) );
			errPrinter.println( String.format("Line %d: Undeclared Indetification: %d => %s", lineNum, lexI, spellingTable.get(lexI)) );
			containsError =true;
			//trick it has been declared to avoid another error report
			AT[lexI] = -1;
			return null;
		}else if( AT[lexI]==-1 ){
			//condition: trick
			//it's a trick index, represent it's declaration is null, so just return null
			return null;
		}else{
			//condition: declared variable
			//just return pointer
			return IT.get( AT[lexI] ).getDecPtr();
		}
	}

	public boolean isContainsError() {
		return containsError;
	}
}
